package xj.graph2d;

import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import xj.app.Application;
import xj.graph2d.graph.AbstractGraph;
import xj.graph2d.graph.Graph;
import xj.graph2d.graph.GraphComponent;
import xj.graph2d.graph.GraphConstants;
import xj.graph2d.graph.GraphEdge;
import xj.graph2d.graph.GraphLayoutManager;
import xj.graph2d.graph.GraphNode;
import xj.graph2d.graph.HyperGraphNode;
import xj.graph2d.graph.Subgraph;
import xj.graph2d.shapes.uml.OrthogonalStateShape;
import xj.graph2d.shapes.uml.UMLSequenceDiagramUtil;
import xj.graph2d.template.UMLDiagramConnectionChecker;
import xj.lang.CodeWriter;
import xj.meta.AbstractModelElement;
import xj.meta.AssociationElement;
import xj.meta.ModelBuilder;
import xj.meta.ModelElement;
import xj.meta.ModelRepository;
import xj.meta.RootElement;
import xj.model.uml.ActivityDiagram;
import xj.model.uml.ClassDiagram;
import xj.model.uml.SequenceDiagram;
import xj.model.uml.StateDiagram;
import xj.model.uml.UMLDiagram;
import xj.model.uml.UseCaseDiagram;

import static xj.graph2d.DrawShapeConstants.*;

public class GraphDocument 
  extends SimpleDrawDocument 
  implements Graph<GraphNodeBase, GraphEdgeBase> {

  public GraphDocument(Graph<GraphNodeBase, GraphEdgeBase> g, Application application) {    
    super(application);
    if (g != null) {
      graph = g;
    } else {
      graph = new AbstractGraph<GraphNodeBase, GraphEdgeBase>();
    }
    graph.setAllowDanglingEdges(true);
    graph.setAllowDuplicateEdges(true);
  }

  public GraphDocument(Application application) {
    this(null, application);
  }

  public GraphDocument(Graph<GraphNodeBase, GraphEdgeBase> g) {
    this(g, null);
  }

  public GraphDocument() {
    this(null, null);
  }

  @Override
  public SimpleDrawDocument makeDocument() { 
    return new GraphDocument(application);
  }

  @Override
  public void initModel() {
    ModelElement model = null;
    if (doctype != null) {
      switch (doctype) {
      case UML_Package_Diagram:
      case UML_Class_Diagram:
	model = new ClassDiagram(name);
	break;
      case UML_Use_Case_Diagram:
	model = new UseCaseDiagram(name);
	break;
      case UML_State_Diagram:
	model = new StateDiagram(name);
	break;
      case UML_Sequence_Diagram:
	model = new SequenceDiagram(name);
	break;
      case UML_Activity_Diagram:
	model = new ActivityDiagram(name);
	break;
      }
    }
    setModel(model);
  }

  public int getType() {
    return GraphConstants.SUBGRAPH;
  }

  public boolean isSubgraph() {
    return true;
  }

  public boolean isNode() {
    return false;
  }

  public boolean isEdge() {
    return false;
  }

  public Graph getGraphComponent() {
    return graph;
  }

  public Graph getGraph() {
    return graph.getGraph();
  }

  public Subgraph<GraphNodeBase, GraphEdgeBase> getSubgraph() {
    return graph.getSubgraph();
  }

  public void setGraph(Graph graph) {
    graph.setGraph(graph);
  }

  public void setSubgraph(Subgraph subgraph) {
    graph.setSubgraph(subgraph);
  }

  public void setAttribute(String name, Object value) {
    graph.setAttribute(name, value);
  }

  public void removeAttribute(String name) {
    graph.removeAttribute(name);
  }

  public Object getAttribute(String key) {
    return graph.getAttribute(key);
  }

  public boolean hasAttribute(String key) {
    return graph.hasAttribute(key);
  }

  public String getLabel() {
    return graph.getLabel();
  }

  public void setLabel(String label) {
    graph.setLabel(label);
  }

  @Override
  public String getName() {
    return (name = graph.getName());
  }

  @Override
  public void setName(String name) {
    this.name = name;  
    graph.setName(name);
    if (model instanceof UMLDiagram) {
      UMLDiagram diagram = (UMLDiagram) model;
      diagram.setName(name);
    }    
  }

  public double getWeight() {
    return graph.getWeight();
  }

  public void setWeight(double weight) {
    graph.setWeight(weight);
  }

  public void setMark(int mark, boolean flag) {
    graph.setMark(mark, flag);
  }

  public boolean getMark(int mark) {
    return graph.getMark(mark);
  }

  public void setAux(Object aux) {
    graph.setAux(aux);
  }

  public Object getAux() {
    return graph.getAux();
  }

  public boolean isDirected() {
    return graph.isDirected();
  }

  public void setDirected(boolean directed) {
    graph.setDirected(directed);
  }

  public boolean isStrict() {
    return graph.isStrict();
  }

  public void setStrict(boolean strict) {
    graph.setStrict(strict);
  }

  public String getId() {
    return graph.getId();
  }

  public void setId(String id) {
    graph.setId(id);
  }

  //
  // Adding and removing shapes
  // 

  protected boolean addGraphNodeOnly(GraphNodeBase node) {
    boolean result = false;
    if (node != null) {
      result = graph.addNode(node);
      node.setGraph(this);
      node.setSubgraph(this);
    }
    return result;
  }

  protected boolean addGraphEdgeOnly(GraphEdgeBase edge, boolean allowDuplicate) {
    boolean result = false;
    if (edge != null) {
      result = graph.addEdge(edge, allowDuplicate);
      edge.setGraph(this);
      edge.setSubgraph(this);
    }
    return result;
  }

  protected void addDrawShapeOnly(DrawShape s, boolean sel) {
    addModel(s);
    super.add(s, sel);
  }

  protected void addDrawShapeOnly(DrawShape s, int i, boolean sel) {
    addModel(s);
    super.add(i, s, sel);
  }

  protected boolean removeGraphNodeOnly(GraphNodeBase node) {
    return removeGraphNodeOnly(node, true);
  }

  protected boolean removeGraphNodeOnly(GraphNodeBase node, boolean disconnect) {
    boolean result = false;
    if (node != null) {
      result = graph.removeNode(node, disconnect);
    }
    return result;
  }

  protected boolean removeGraphEdgeOnly(GraphEdgeBase edge) {
    boolean result = false;
    if (edge != null) {
      result = graph.removeEdge(edge);
    }
    return result;
  }

  protected void removeDrawShapeOnly(DrawShape s) {
    super.remove(s);
  }

  public boolean addNode(GraphNodeBase node) {
    boolean result = false;
    if (node != null) {
      result = graph.addNode(node);
      node.setGraph(this);
      node.setSubgraph(this);
    }
    if (node instanceof DrawShape) {
      addDrawShapeOnly((DrawShape) node, 0, false);
    }
    return result;
  }

  @Override
  public void add(DrawShape s, boolean sel) {
    if (s != null) {
      if (s instanceof GraphNodeBase) {
	if (addGraphNodeOnly((GraphNodeBase) s)) {
	  if (sel) {
	    selected = s;
	  }
	}
      } else if (s instanceof GraphEdgeBase) {
	if (addGraphEdgeOnly((GraphEdgeBase) s, isAllowDuplicateEdges())) {
	  if (sel) {
	    selected = s;
	  }
	}
      }
      addModel(s);
      super.add(s, sel);
    }
  }

  protected void addModel(DrawShape s) {
    if (s != null && model instanceof UMLDiagram) {
      ModelElement smodel = s.getModel();
      ModelElement pmodel = null;
      if (smodel != null) {
	UMLDiagram diagram = (UMLDiagram) model;
	DrawShape parent = s.getParent();
	if (parent != null) {
	  pmodel = parent.getModel();
	}
	diagram.add(pmodel, smodel);
      }
    }
  }

  @Override
  public void remove(DrawShape s) {
    remove(s, true);
  }

  public void remove(DrawShape s, boolean disconnect) {
    if (s != null) {
      if (s instanceof GraphNodeBase) {
	if (removeGraphNodeOnly((GraphNodeBase) s, disconnect)) {
	  // return;
	}
      } else if (s instanceof GraphEdgeBase) {
	if (removeGraphEdgeOnly((GraphEdgeBase) s)) {
	  // return;
	}
      }
      super.remove(s);
    }
  }

  @Override
  public void removeAll() {
    removeAllNodes();
    super.removeAll();
  }

  public boolean removeNode(GraphNodeBase node) {
    return removeNode(node, true);
  }

  public boolean removeNode(GraphNodeBase node, boolean disconnect) {
    boolean result = false;
    if (node != null) {
      result = graph.removeNode(node, disconnect);
    }
    if (node instanceof DrawShape) {
      removeDrawShapeOnly((DrawShape) node);
    }
    /*
     * Iterator edges = leavingEdges(node); while (edges.hasNext()) {
     * GraphEdge e = (GraphEdge) edges.next(); if (e instanceof DrawShape) {
     * removeDrawShapeOnly((DrawShape) e); } } edges = arrivingEdges(node);
     * while (edges.hasNext()) { GraphEdge e = (GraphEdge) edges.next(); if
     * (e instanceof DrawShape) { removeDrawShapeOnly((DrawShape) e); } }
     */
    return result;
  }

  public GraphNodeBase removeNode(String name) {
    return removeNode(name, true);
  }

  public GraphNodeBase removeNode(String name, boolean disconnect) {
    GraphNodeBase node = graph.removeNode(name, disconnect);
    if (node != null) {
      removeDrawShapeOnly((DrawShape) node);
    }
    return node;
  }

  public boolean addEdge(GraphEdgeBase edge) {
    return addEdge(edge, isAllowDuplicateEdges());
  }

  public boolean addEdge(GraphEdgeBase edge, boolean allowDuplicate) {
    boolean result = false;
    if (edge != null) {
      result = graph.addEdge(edge, allowDuplicate);
      edge.setGraph(this);
      edge.setSubgraph(this);
    }
    if (edge instanceof DrawShape) {
      addDrawShapeOnly((DrawShape) edge, false);
    }
    return result;
  }

  public boolean removeEdge(GraphEdgeBase edge) {
    boolean result = false;
    if (edge != null) {
      result = graph.removeEdge(edge);
    }
    if (edge instanceof DrawShape) {
      removeDrawShapeOnly((DrawShape) edge);
    }
    return result;
  }

  public void replaceEdge(DrawShape oldEdge, DrawShape newEdge) {
    if (oldEdge != null && newEdge != null 
	&& oldEdge instanceof GraphEdgeBase
	&& newEdge instanceof GraphEdgeBase) {
      GraphEdgeBase e1 = (GraphEdgeBase) oldEdge;
      GraphEdgeBase e2 = (GraphEdgeBase) newEdge;
      GraphNodeBase src = graph.disconnectSource(e1);
      GraphNodeBase des = graph.disconnectDestination(e1);
      if (src != null) {
	graph.connectSource(e2, src);
      }
      if (des != null) {
	graph.connectDestination(e2, des);
      }

      ModelElement emodel = e1.getModel();
      e2.setModel(emodel);

      e2.setEnds(e1.getEnd1(), e1.getEnd2());
      removeEdge(e1);
      addEdge(e2);
      e2.doLayout();
    }
  }

  public GraphNodeBase disconnectDestination(GraphEdgeBase edge) {
    if (debug) {
      System.out.println("GraphDocument.disconnectDestination()");
    }

    GraphNodeBase node = graph.disconnectDestination(edge);
    edgeParentAdjustment(edge);
    if (edge instanceof GraphEdgeBase) {
      GraphEdgeBase e = (GraphEdgeBase) edge;
      ModelElement me = e.getModel();
      if (me instanceof AssociationElement) {
	AssociationElement a = (AssociationElement) me;
	a.setRightElement(null);
      }
    }

    return node;
  }

  public GraphNodeBase disconnectSource(GraphEdgeBase edge) {
    if (debug) {
      System.out.println("GraphDocument.disconnectSource()");
    }

    GraphNodeBase node = graph.disconnectSource(edge);
    edgeParentAdjustment(edge);
    if (edge instanceof GraphEdgeBase) {
      GraphEdgeBase e = (GraphEdgeBase) edge;
      ModelElement me = e.getModel();
      if (me instanceof AssociationElement) {
	AssociationElement a = (AssociationElement) me;
	a.setLeftElement(null);
      }
    }

    return node;
  }

  public void connectDestination(GraphEdgeBase edge, GraphNodeBase node) {
    if (debug) System.out.println("GraphDocument.connectDestination()");

    if (checkConnectDestination(edge, node)) { 
      graph.connectDestination(edge, node);
      edgeParentAdjustment(edge);
      edge.adjustHeadOffset(edge.getEnd2(), node);

      ModelElement me = edge.getModel();
      ModelElement mn = node.getModel();
      if (me instanceof AssociationElement) {
	AssociationElement a = (AssociationElement) me;
	a.setRightElement(mn);
      }
    }
  }

  public void connectSource(GraphEdgeBase edge, GraphNodeBase node) {
    if (debug) System.out.println("GraphDocument.connectSource()");

    if (checkConnectSource(edge, node)) { 
      graph.connectSource(edge, node);
      edgeParentAdjustment(edge);
      edge.adjustTailOffset(edge.getEnd1(), node);

      ModelElement me = edge.getModel();
      ModelElement mn = node.getModel();
      if (me instanceof AssociationElement) {
	AssociationElement a = (AssociationElement) me;
	a.setLeftElement(mn);
      }
    }
  }

  // check if it is allowed to connect edge to node as the source
  public boolean checkConnectSource(GraphEdgeBase edge, GraphNodeBase node) {
    //System.out.println("GraphDocument.checkConnectSource()");
    if (getDocumentType() != Document.DocumentType.Generic_Diagram && 
	edge != null && 
	node != null) { 
      GraphNodeBase des = edge.getDestination();
      // check if it is forming a self-loop
      //System.out.println("GraphDocument.checkConnectSource() des == node " + (des == node));
      //System.out.println("GraphDocument.checkConnectSource() edge.isAllowSelfLoop() " + edge.isAllowSelfLoop());
      if (des == node && 
	  !edge.isAllowSelfLoop())  {
	return false; 
      }

      if (model != null) { 
	return UMLDiagramConnectionChecker.checkConnectSource(doctype, model, edge, node);
      }    

      return true;
    } 
    return false; 
  }

  // check if it is allowed to connect edge to node as the destination
  public boolean checkConnectDestination(GraphEdgeBase edge, GraphNodeBase node) {
    //System.out.println("GraphDocument.checkConnectDestination()");
    if (getDocumentType() != DocumentType.Generic_Diagram && 
	edge != null && 
	node != null) { 
      GraphNodeBase src = edge.getSource();
      // check if it is forming a self-loop
      //System.out.println("GraphDocument.checkConnectDestination() src == node " + (src == node));
      //System.out.println("GraphDocument.checkConnectDestination() edge.isAllowSelfLoop() " + edge.isAllowSelfLoop());
      if (src == node && 
	  !edge.isAllowSelfLoop()) {
	return false; 
      }
    
      if (model != null) { 
	return UMLDiagramConnectionChecker.checkConnectDestination(doctype, model, edge, node);
      }
      
      return true;
    } 
    return false; 
  }

  public boolean checkAcceptSubnode(GraphNodeBase parent, GraphNodeBase node) { 
    if (model != null) { 
      ModelElement pmodel = parent.getModel();
      ModelElement nmodel = node.getModel();
      if (pmodel != null && 
	  nmodel != null) { 
	return UMLDiagramConnectionChecker.checkAcceptSubnode(doctype, model, pmodel, nmodel);
      }
    }    
    return true; 
  }

  public void reverseEdgeDirection(GraphEdgeBase edge) { 
    if (edge != null) {
      GraphNodeBase src = disconnectSource(edge);
      GraphNodeBase des = disconnectDestination(edge);

      if ((src != null || des != null) && 
	  src != des) { 
	edge.swapEnds();
	
	if (src != null) { 
	  connectDestination(edge, src); 
	}
	if (des != null) { 
	  connectSource(edge, des); 
	}
	
	edge.adjustEnds(false);
	edge.layoutComponents();
	//edge.doLayout();
      }
    }
  }

  public void flipEdgeOrientation(GraphEdgeBase edge) { 
    if (edge != null && 
	edge.isFlippable()) { 
      edge.flipOrientation(); 
      edge.adjustEnds(false);
      edge.layoutComponents();
    }
  }

  public void moveEdgeToSubgraph(GraphEdgeBase e, Subgraph<GraphNodeBase, GraphEdgeBase> subg) {
    if (e != null && 
	subg != null) { 
      if (e instanceof GraphEdgeBase && 
	  (subg instanceof GraphDocument ||
	   subg instanceof GraphHyperNodeBase)) {
	GraphEdgeBase edge = (GraphEdgeBase) e;
	removeDrawShapeOnly(edge);
	shapeRemoved(edge); 

	if (subg instanceof GraphDocument) { 
	  GraphDocument gdoc = (GraphDocument) subg;
	  gdoc.addDrawShapeOnly(edge, false);
	  gdoc.shapeAdded(edge);
	} else if (subg instanceof GraphHyperNodeBase) { 
	  GraphHyperNodeBase hnode = (GraphHyperNodeBase) subg; 
	  hnode.addShape(edge, false, true); // add shape only 
	  shapeAdded(edge); 
	}
      }

      if (graph != null) { 
	graph.moveEdgeToSubgraph(e, subg);
      }
    }
  }

  protected static List<Subgraph<GraphNodeBase, GraphEdgeBase>> getAncestors(GraphNodeBase n) {
    if (n != null) {
      List<Subgraph<GraphNodeBase, GraphEdgeBase>> path =
	new ArrayList<Subgraph<GraphNodeBase, GraphEdgeBase>>();
      Subgraph<GraphNodeBase, GraphEdgeBase> p = n.getSubgraph();
      while (p != null) {
	path.add(0, p);
	p = p.getSubgraph();
      }
      return path;
    }
    return null;
  }

  protected static Subgraph<GraphNodeBase, GraphEdgeBase> 
    findLeastCommonAncestor(GraphNodeBase n1,
			    GraphNodeBase n2) {
    if (n1 != null && n2 != null) {
      if (n1 == n2) {
	return n1.getSubgraph();
      }

      List<Subgraph<GraphNodeBase, GraphEdgeBase>> l1 = getAncestors(n1);
      List<Subgraph<GraphNodeBase, GraphEdgeBase>> l2 = getAncestors(n2);
      if (l1 != null && l2 != null) {
	int len1 = l1.size();
	int len2 = l2.size();
	if (len1 > 0 && len2 > 0) {
	  Subgraph<GraphNodeBase, GraphEdgeBase> c = null;
	  int len = Math.min(len1, len2);
	  for (int i = 0; i < len; i++) {
	    Subgraph<GraphNodeBase, GraphEdgeBase> r1 = l1.get(i);
	    Subgraph<GraphNodeBase, GraphEdgeBase> r2 = l2.get(i);
	    if (r1 == r2) {
	      c = r1;
	    } else {
	      break;
	    }
	  }
	  return c;
	}
      }
    }
    return null;
  }

  /*
   * ensure the edge is placed in the proper graph, the least common ancestor of src and des nodes
   */
  public void edgeParentAdjustment(GraphEdgeBase e) { 
    if (debug) { 
      System.out.println("GraphDocument.edgeParentAdjustment()");
    }

    if (e != null) { 
      GraphNodeBase src = e.getSource();
      GraphNodeBase des = e.getDestination();
      if (src != null && des != null) { 
	Subgraph<GraphNodeBase, GraphEdgeBase> parent = e.getSubgraph(); 
	if (parent == null) { 
	  parent = e.getGraph();
	} 
	Subgraph<GraphNodeBase, GraphEdgeBase> subg = findLeastCommonAncestor(src, des);

	if (debug) { 
	  System.out.println("GraphDocument.edgeParentAdjustment(): parent " +
			     (parent == null ?  "null" : parent.getClass().getName() + " " + parent));
	  System.out.println("GraphDocument.edgeParentAdjustment(): subg " +
			     (subg == null ?  "null" : subg.getClass().getName() + " " + subg));
	}

	if (subg != parent) { 	  
	  moveEdgeToSubgraph(e, parent, subg);
	} 
      }
    }
  }

  protected void moveEdgeToSubgraph(GraphEdgeBase e, 
				    Subgraph<GraphNodeBase, GraphEdgeBase> parent, 
				    Subgraph<GraphNodeBase, GraphEdgeBase> subg) { 
    if (debug) { 
      System.out.println("GraphDocument.moveEdgeToSubgraph()");
    }

    if (e != null && 
	subg != parent) {  
      if (parent == null || parent == this) {
	if (subg != null && subg != this) { 
	  moveEdgeToSubgraph(e, subg); 
	}
      } else {
	if (subg == null) { 
	  subg = this;
	}
	if (parent instanceof GraphDocument) {
	  GraphDocument gdoc = (GraphDocument) parent;
	  gdoc.moveEdgeToSubgraph(e, subg); 
	} else if (parent instanceof GraphHyperNodeBase) {
	  GraphHyperNodeBase pnode = (GraphHyperNodeBase) parent;
	  //pnode.moveEdgeToSubgraph(e, subg); 
	}
	if (e instanceof GraphEdgeBase) { 
	  GraphEdgeBase edge = (GraphEdgeBase) e;
	  shapeRemoved(edge); 
	  shapeAdded(edge); 
	}
      }
    }
  }

  public boolean isAllowDanglingEdges() {
    return graph.isAllowDanglingEdges();
  }

  public void setAllowDanglingEdges(boolean allowDanglingEdges) {
    graph.setAllowDanglingEdges(allowDanglingEdges);
  }

  public boolean isAllowDuplicateEdges() {
    return graph.isAllowDuplicateEdges();
  }

  public void setAllowDuplicateEdges(boolean allowDuplicateEdges) {
    graph.setAllowDuplicateEdges(allowDuplicateEdges);
  }

  public GraphEdgeBase removeEdge(String name) {
    GraphEdgeBase edge = graph.removeEdge(name);
    if (edge != null) {
      removeDrawShapeOnly((DrawShape) edge);
    }
    return edge;
  }

  public void removeAllNodes() {
    graph.removeAllNodes();
  }

  public boolean addSubgraph(Subgraph<GraphNodeBase, GraphEdgeBase> subg) {
    return graph.addSubgraph(subg);
  }

  public boolean removeSubgraph(Subgraph<GraphNodeBase, GraphEdgeBase> subg) {
    return graph.removeSubgraph(subg);
  }

  public boolean removeSubgraph(String name) {
    return graph.removeSubgraph(name);
  }

  public boolean containsNode(GraphNodeBase node) {
    return graph.containsNode(node);
  }

  public boolean containsEdge(GraphEdgeBase edge) {
    return graph.containsEdge(edge);
  }

  public Set<GraphNodeBase> allSubnodes() {
    return graph.allSubnodes();
  }

  public boolean containsSubnode(GraphNodeBase node) {
    return graph.containsSubnode(node);
  }

  public GraphNodeBase findNodeByName(String name) {
    return graph.findNodeByName(name);
  }

  public GraphNodeBase findNodeByName(String name, boolean deep) {
    return graph.findNodeByName(name, deep);
  }

  public GraphEdgeBase findEdgeByName(String name) {
    return graph.findEdgeByName(name);
  }

  public GraphEdgeBase findEdgeByName(String name, boolean deep) {
    return graph.findEdgeByName(name, deep);
  }

  public Subgraph<GraphNodeBase, GraphEdgeBase> findSubgraphByName(String name) {
    return graph.findSubgraphByName(name);
  }

  public Subgraph<GraphNodeBase, GraphEdgeBase> findSubgraphByName(String name, boolean deep) {
    return graph.findSubgraphByName(name, deep);
  }

  public int getNumberOfNodes() {
    return graph.getNumberOfNodes();
  }

  public int getNumberOfEdges() {
    return graph.getNumberOfEdges();
  }

  public boolean isEmpty() {
    return graph.isEmpty();
  }

  public Iterator<GraphNodeBase> nodes() {
    return graph.nodes();
  }

  public Iterator<GraphEdgeBase> edges() {
    return graph.edges();
  }

  public Iterator<Subgraph<GraphNodeBase, GraphEdgeBase>> subgraphs() {
    return graph.subgraphs();
  }

  public Set<GraphNodeBase> getNodeSet() {
    return graph.getNodeSet();
  }

  public Set<GraphEdgeBase> getEdgeSet() {
    return graph.getEdgeSet();
  }

  public Set<Subgraph<GraphNodeBase, GraphEdgeBase>> getSubgraphSet() {
    return graph.getSubgraphSet();
  }

  public Set<GraphEdgeBase> leavingEdges(GraphNodeBase node) {
    return graph.leavingEdges(node);
  }

  public Set<GraphEdgeBase> leavingEdges(GraphNodeBase node, int edgeType) {
    return graph.leavingEdges(node, edgeType);
  }

  public Set<GraphEdgeBase> arrivingEdges(GraphNodeBase node) {
    return graph.arrivingEdges(node);
  }

  public Set<GraphEdgeBase> arrivingEdges(GraphNodeBase node, int edgeType) {
    return graph.arrivingEdges(node, edgeType);
  }

  public GraphNodeBase searchNode(Point2D p) {
    DrawShapeSearch.Result result = 
      DrawShapeSearch.search((Iterator) nodes(), p,
			     DrawShapeSearch.GRAPH_NODE);
    if (result != null && 
	result.shape != null) {
      DrawShape shape = result.shape;
      
      if (debug) { 
	System.out.println("GraphDocument.searchNode(): " + shape.getClass().getName());
      }

      while (shape != null) {
	if (shape instanceof GraphNodeBase) {
	  return (GraphNodeBase) shape;
	} else {
	  shape = shape.getParent();
	}
      }
    }
    return null;
    // return graph.searchNode(p);
  }

  public GraphEdgeBase searchEdge(Point2D p) {
    GraphEdgeBase result = null;
    if (p != null) {
      for (Iterator iter = edges(); iter.hasNext();) {
	Object o = iter.next();
	if (o instanceof GraphEdgeBase) {
	  GraphEdgeBase e = (GraphEdgeBase) o;
	  int pos = e.isInside(p);
	  if (pos != PointOutside) {
	    result = e;
	    break;
	  }
	}
      }
    }
    return result;
  }

  public GraphNodeBase searchNodeHotSpot(Point2D p) {
    GraphNodeBase result = null;
    if (p != null) {
      for (Iterator iter = nodes(); iter.hasNext();) {
	Object o = iter.next();
	if (o instanceof GraphNodeBase) {
	  GraphNodeBase n = (GraphNodeBase) o;
	  int pos = n.isOnHotSpot(p);
	  if (pos != HOTSPOT_NONE) {
	    result = n;
	    break;
	  }
	}
      }
    }
    return result;
  }

  public GraphEdgeBase searchEdgeHotSpot(Point2D p) {
    GraphEdgeBase result = null;
    if (p != null) {
      for (Iterator iter = edges(); iter.hasNext();) {
	Object o = iter.next();
	if (o instanceof GraphEdgeBase) {
	  GraphEdgeBase e = (GraphEdgeBase) o;
	  int pos = e.isOnHotSpot(p);
	  if (pos != HOTSPOT_NONE) {
	    result = e;
	    break;
	  }
	}
      }
    }
    return result;
  }

  public GraphComponentShape searchHotSpot(Point2D p) {
    if (p != null) {
      GraphEdgeBase e = searchEdgeHotSpot(p);
      if (e != null) {
	return e;
      }
      return searchNodeHotSpot(p);
    }
    return null;
  }

  public boolean isReachable(GraphNodeBase src, GraphNodeBase des) {
    return graph.isReachable(src, des);
  }

  public int getDegree(GraphNodeBase node) {
    return graph.getDegree(node);
  }

  public int getInDegree(GraphNodeBase node) {
    return graph.getInDegree(node);
  }

  public int getOutDegree(GraphNodeBase node) {
    return graph.getOutDegree(node);
  }

  public GraphNodeBase getSelectedNode() {
    return selectedNode;
  }

  public void setSelectedNode(GraphNodeBase node) {
    selectedNode = node;
  }

  public void move(double dx, double dy) {
    graph.move(dx, dy);
  }

  public void setGraphLayoutManager(String name) {
    if (name != null) { 
      if ("xj.graph2d.layout.ClassDiagramLayout".equals(name)) { 
	setGraphLayoutManager(new xj.graph2d.layout.ClassDiagramLayout(this));   
      } else if ("xj.graph2d.layout.PackageDiagramLayout".equals(name)) {
	setGraphLayoutManager(new xj.graph2d.layout.PackageDiagramLayout(this));   
      } else if ("xj.graph2d.layout.StateDiagramLayout".equals(name)) {
	setGraphLayoutManager(new xj.graph2d.layout.StateDiagramLayout(this));   
      } else if ("xj.graph2d.layout.VisualClassLayout".equals(name)) {
	setGraphLayoutManager(new xj.graph2d.layout.VisualClassLayout(this)); 
      }
    }
  }

  public void setGraphLayoutManager(GraphLayoutManager g) {
    graph.setGraphLayoutManager(g);
  }

  public void setViewOptions(String name) {
    if (name != null) { 
      if ("xj.graph2d.app.diagram.ClassDiagramViewOptions".equals(name)) { 
	setViewOptions(new xj.graph2d.app.diagram.ClassDiagramViewOptions(this));       
      }
    }
  }

  public GraphLayoutManager getGraphLayoutManager() {
    return graph.getGraphLayoutManager();
  }

  public boolean isContinuousLayout() { 
    return continuousLayout;
  }

  public void setContinuousLayout(boolean continuousLayout) { 
    this.continuousLayout = continuousLayout;
  }

  @Override
  public void shapeAdded(DrawShape s) {
    super.shapeAdded(s);

    if (s != null) {
      DrawShape parent = s.getParent();
      if (s instanceof GraphComponent && !(parent instanceof HyperGraphNode)) {
	if (s instanceof GraphNodeBase) {
	  addGraphNodeOnly((GraphNodeBase) s);
	} else if (s instanceof GraphEdgeBase) {
	  addGraphEdgeOnly((GraphEdgeBase) s, true);
	}
      }
    }
  }

  public void doLayout() {
    graph.doLayout();
  }

  public void layoutEdges() {
    Iterator edges = graph.edges();
    while (edges.hasNext()) {
      GraphEdge edge = (GraphEdge) edges.next();
      if (edge instanceof GraphEdgeBase) {
	GraphEdgeBase e = (GraphEdgeBase) edge;
	// edge.adjustEnds();
	e.doLayout();
      }
    }
  }

  public void layoutAdjacentEdges(GraphNode node) {
    if (node != null) {
      // Iterator edges = leavingEdgesInParent(node, this);
      Set edgeSet = node.leavingEdges();
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge edge = (GraphEdge) edges.next();
	  if (edge != null) {
	    edge.adjustEnds();
	  }
	}
      }

      // edges = arrivingEdgesInParent(node, this);
      edgeSet = node.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge edge = (GraphEdge) edges.next();
	  if (edge != null) {
	    edge.adjustEnds();
	  }
	}
      }
    }
  }

  public void setVisibleAdjacentEdges(GraphNode node, boolean f,
				      DrawCanvas canvas) {
    if (node != null) {
      // Iterator edges = graph.leavingEdges(node);
      // Iterator edges = leavingEdgesInParent(node, this);
      Set edgeSet = node.leavingEdges();
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    e.setVisible(f);
	    if (canvas != null) {
	      canvas.repaintRect(e.getRepaintRect(), true);
	      if (debug) {
		System.out.println(" edge: " + e.getClass().getName()
		    + " repaintRect=" + e.getRepaintRect());
	      }
	    }
	  }
	}
      }

      // edges = graph.arrivingEdges(node);
      // edges = arrivingEdgesInParent(node, this);
      edgeSet = node.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    e.setVisible(f);
	    if (canvas != null) {
	      canvas.repaintRect(e.getRepaintRect(), true);
	      if (debug) {
		System.out.println(" edge: " + e.getClass().getName()
		    + " repaintRect=" + e.getRepaintRect());
	      }
	    }
	  }
	}
      }
    }
  }

  public void drawAdjacentEdges(GraphNode node, Graphics2D g2) {
    drawAdjacentEdges(node, null, g2);
  }

  /**
   * Draw all edges adjacent to node, except edge
   */
  public void drawAdjacentEdges(GraphNode node, GraphEdge edge, Graphics2D g2) {
    GraphNode edgeSrc = null;
    GraphNode edgeDes = null;
    if (edge != null) {
      edgeSrc = edge.getSource();
      edgeDes = edge.getDestination();
    }

    if (node != null) {
      // Iterator edges = graph.leavingEdges(node);
      // Iterator edges = leavingEdgesInParent(node, this);
      Set edgeSet = node.leavingEdges();
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o != edge && o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode src = e.getSource();
	    GraphNode des = e.getDestination();
	    if (edgeSrc == null
		|| edgeDes == null
		|| !(edgeSrc == src && edgeDes == des || edgeSrc == des
		    && edgeDes == src)) {
	      e.drawShape(g2);
	    }
	  }
	}
      }

      // edges = graph.arrivingEdges(node);
      // edges = arrivingEdgesInParent(node, this);
      edgeSet = node.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o != edge && o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode src = e.getSource();
	    GraphNode des = e.getDestination();
	    if (edgeSrc == null
		|| edgeDes == null
		|| !(edgeSrc == src && edgeDes == des || edgeSrc == des
		    && edgeDes == src)) {
	      e.drawShape(g2);
	    }
	  }
	}
      }
    }
  }

  public void repaintRectAdjacentEdges(GraphNode node, DrawCanvas canvas) {
    if (node != null) {
      // Iterator edges = graph.leavingEdges(node);
      // Iterator edges = leavingEdgesInParent(node, this);
      Set edgeSet = node.leavingEdges();
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    canvas.repaintRect(((GraphEdgeBase) o).getRepaintRect(), true);
	  }
	}
      }

      // edges = graph.arrivingEdges(node);
      // edges = arrivingEdgesInParent(node, this);
      edgeSet = node.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    canvas.repaintRect(((GraphEdgeBase) o).getRepaintRect(), true);
	  }
	}
      }
    }
  }

  public List<GraphEdgeBase> findAllEdgesBetween(GraphNode node1,
						 GraphNode node2) {
    List<GraphEdgeBase> result = new ArrayList<GraphEdgeBase>();
    if (node1 != null && node2 != null) {
      // Iterator edges = graph.leavingEdges(node1);
      // Iterator edges = leavingEdgesInParent(node1, this);
      Set edgeSet = node1.leavingEdges();
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase edge = (GraphEdgeBase) o;
	    GraphNode n = edge.getDestination();
	    if (n == node2) {
	      result.add(edge);
	    }
	  }
	}
      }

      // edges = graph.arrivingEdges(node1);
      // edges = arrivingEdgesInParent(node1, this);
      edgeSet = node1.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  Object o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase edge = (GraphEdgeBase) o;
	    GraphNode n = edge.getSource();
	    if (n == node2) {
	      result.add(edge);
	    }
	  }
	}
      }
    }
    return result;
  }

  public static Set<GraphEdgeBase> edgesBetween(GraphNode node1, GraphNode node2) {
    Set<GraphEdgeBase> result = null;
    if (node1 != null && node2 != null) {
      result = new HashSet<GraphEdgeBase>();
      Set<GraphEdge> edgeSet = node1.leavingEdges();
      Iterator<GraphEdge> edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getDestination();
	    if (n == node2) {
	      result.add(e);
	    }
	  }
	}
      }

      edgeSet = node1.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getSource();
	    if (n == node2) {
	      result.add(e);
	    }
	  }
	}
      }
    }
    return result;
  }

  public static boolean isConnected(GraphNode node1, GraphNode node2) { 
    if (node1 != null && node2 != null) {
      Set<GraphEdge> edgeSet = node1.leavingEdges();
      Iterator<GraphEdge> edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getDestination();
	    if (n == node2) {
	      return true;
	    }
	  }
	}
      }

      edgeSet = node1.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getSource();
	    if (n == node2) {
	      return true;
	    }
	  }
	}
      }
    }
    return false;
  }

  public void drawEdgesBetween(GraphNode node1, GraphNode node2,
			       GraphEdge edge, Graphics2D g2) {
    // System.out.println("drawEdgesBetween() @1");
    if (node1 != null && node2 != null) {
      // Iterator edges = graph.leavingEdges(node1);
      // Iterator edges = leavingEdgesInParent(node1, this);
      Set<GraphEdge> edgeSet = node1.leavingEdges();
      Iterator<GraphEdge> edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getDestination();
	    if (n == node2 && e != edge) {
	      e.drawShape(g2);
	    }
	  }
	}
      }

      // edges = graph.arrivingEdges(node1);
      // edges = arrivingEdgesInParent(node1, this);
      edgeSet = node1.arrivingEdges();
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge o = edges.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    GraphNode n = e.getSource();
	    if (n == node2 && e != edge) {
	      e.drawShape(g2);
	    }
	  }
	}
      }
    }
  }

  public static Set<GraphEdgeBase> allAdjacentEdges(Iterator<DrawShape> iter) {
    if (iter != null) {
      Set<GraphNodeBase> excludes = new HashSet<GraphNodeBase>();
      Set<GraphEdgeBase> set = new HashSet<GraphEdgeBase>();
      while (iter.hasNext()) {
	DrawShape s = iter.next();
	if (s instanceof GraphNodeBase) {
	  GraphNodeBase node = (GraphNodeBase) s;
	  allAdjacentEdges(node, set, excludes);
	}
      }
      return set;
    }
    return null;
  }

  /* all edges adjacent to the node and all its subnodes */
  public static Set<GraphEdgeBase> allAdjacentEdges(GraphNodeBase node) {
    return allAdjacentEdges(node, null, null);
  }

  public static Set<GraphEdgeBase> allAdjacentEdges(GraphNodeBase node, Set<GraphEdgeBase> set) {
    return allAdjacentEdges(node, set, null);
  }

  public static Set<GraphEdgeBase> allAdjacentEdges(GraphNodeBase node,
						    Set<GraphEdgeBase> set, 
						    Set<GraphNodeBase> excludes) {
    if (node != null) {
      if (debug) {
	System.out.println("GraphDocument.allAdjacentEdges() @1 node=" + node.getId());
      }

      if (excludes != null && excludes.contains(node)) {
	return set;
      }

      if (set == null) {
	set = new HashSet<GraphEdgeBase>();
      }
      if (excludes == null) {
	excludes = new HashSet<GraphNodeBase>();
      }
      excludes.add(node);

      Set<GraphEdge> edgeSet = node.leavingEdges();
      Iterator<GraphEdge> iter;
      if (edgeSet != null) {
	if (debug) {
	  System.out.println("GraphDocument.allAdjacentEdges() @2 node="
			     + node.getId());
	}
	iter = edgeSet.iterator();
	while (iter.hasNext()) {
	  GraphEdge o = iter.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    set.add(e);
	    allAdjacentEdges(e, set, excludes);
	  }
	}
      }

      edgeSet = node.arrivingEdges();
      if (edgeSet != null) {
	if (debug) {
	  System.out.println("GraphDocument.allAdjacentEdges() @3 node="
			     + node.getId());
	}
	iter = edgeSet.iterator();
	while (iter.hasNext()) {
	  GraphEdge o = iter.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    set.add(e);
	    allAdjacentEdges(e, set, excludes);
	  }
	}
      }

      if (node instanceof Subgraph) {
	if (debug) {
	  System.out.println("GraphDocument.allAdjacentEdges() @4 node=" + node.getId());
	}
	Subgraph<GraphNodeBase, GraphEdgeBase> sg = (Subgraph<GraphNodeBase, GraphEdgeBase>) node;
	Iterator<GraphNodeBase> niter = sg.nodes();
	if (niter != null) {
	  while (niter.hasNext()) {
	    GraphNodeBase n = niter.next();
	    allAdjacentEdges(n, set, excludes);
	  }
	}
      } else {
	Iterator<DrawShape> siter = node.allShapes();
	if (siter != null) {
	  while (siter.hasNext()) {
	    DrawShape s = siter.next();
	    if (s instanceof GraphNodeBase) {
	      allAdjacentEdges((GraphNodeBase) s, set, excludes);
	    }
	  }
	}
      }
    }
    return set;
  }

  public static Set allAdjacentEdges(GraphEdgeBase edge, Set set) {
    return allAdjacentEdges(edge, set, null);
  }

  public static Set allAdjacentEdges(GraphEdgeBase edge, Set set, Set excludes) {
    if (edge != null) {
      if (debug) {
	System.out.println("GraphDocument.allAdjacentEdges() @5  edge="
			   + edge.getId());
      }
      if (excludes != null && excludes.contains(edge)) {
	return set;
      }

      if (set == null) {
	set = new HashSet();
      }
      if (excludes == null) {
	excludes = new HashSet();
      }
      excludes.add(edge);

      Iterator<DrawShape> iter = edge.allShapes();
      if (iter != null) {
	while (iter.hasNext()) {
	  DrawShape s = iter.next();
	  if (s instanceof GraphNodeBase) {
	    allAdjacentEdges((GraphNodeBase) s, set, excludes);
	  }
	}
      }
    }
    return set;
  }

  public static void setEdgesVisible(Set edges, boolean f, DrawCanvas canvas) {
    if (edges != null) {
      Iterator iter = edges.iterator();
      if (iter != null) {
	while (iter.hasNext()) {
	  Object o = iter.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    e.setVisible(f);
	    if (canvas != null) {
	      canvas.repaintRect(e.getRepaintRect(), true);
	    }
	  }
	}
      }
    }
  }

  public static void drawEdges(Set edges, Graphics2D g2) {
    if (edges != null) {
      Iterator iter = edges.iterator();
      if (iter != null) {
	while (iter.hasNext()) {
	  Object o = iter.next();
	  if (o instanceof GraphEdgeBase) {
	    GraphEdgeBase e = (GraphEdgeBase) o;
	    e.drawShape(g2);
	  }
	}
      }
    }
  }

  public static void layoutEdges(Set<GraphEdgeBase> edges) {
    //System.out.println("GraphDocument.layoutEdges() size=" + edges.size());
    if (edges != null) {
      Iterator<GraphEdgeBase> iter = edges.iterator();
      if (iter != null) {
	while (iter.hasNext()) {
	  GraphEdgeBase e = iter.next();
	  //System.out.println("GraphDocument.layoutEdges() e=" + e);
	  e.doLayout();
	}
      }
    }
  }

  public void setNodeAttribute(String name, Object value) {
    graph.setNodeAttribute(name, value);
  }

  public void removeNodeAttribute(String name) {
    graph.removeNodeAttribute(name);
  }

  public Object getNodeAttribute(String key) {
    return graph.getNodeAttribute(key);
  }

  public void setEdgeAttribute(String name, Object value) {
    graph.setEdgeAttribute(name, value);
  }

  public void removeEdgeAttribute(String name) {
    graph.removeEdgeAttribute(name);
  }

  public Object getEdgeAttribute(String key) {
    return graph.getEdgeAttribute(key);
  }

  public void printSubgraph(PrintWriter out) {
    graph.printSubgraph(out);
  }

  public void print() {
    graph.print();
  }

  public void printSubgraph(int level) {
    graph.printSubgraph(level);
  }

  public void printGraph(PrintWriter output) {
    graph.printGraph(output);
  }

  public void printGraph(OutputStream output) {
    // graph.prinGraph(output);
  }

  public String getIndent() {
    return graph.getIndent();
  }

  public void incrementIndent() {
    graph.incrementIndent();
  }

  public void decrementIndent() {
    graph.decrementIndent();
  }

  /*
   * public void makeGroup(Group g, boolean fit) {
   * 
   * System.out.println("GraphDocument.makeGroup()");
   * 
   * group = g; if (group != null) { Iterator iter; iter = graph.nodes();
   * while (iter.hasNext()) { GraphNodeBase shape = (GraphNodeBase)
   * iter.next(); if (shape != null && !shape.isZeroSize()) { if
   * (group.contains(shape)) { group.addShape(shape); } } } iter =
   * graph.edges(); while (iter.hasNext()) { GraphEdgeBase shape =
   * (GraphEdgeBase) iter.next(); if (shape != null &&
   * !shape.isZeroSize()) { if (group.contains(shape)) {
   * group.addShape(shape); } } }
   * 
   * iter = shapes.iterator(); while (iter.hasNext()) { DrawShape shape =
   * (DrawShape) iter.next(); if (shape != null && !shape.isZeroSize()) {
   * if (group.contains(shape)) { group.addShape(shape); iter.remove(); } } } }
   * if (fit) { group.fitComponents(); } }
   */

  //
  // Alignment and layout
  //

  public enum Alignment {
    Top, Bottom, Left, Right, Center, CenterV, CenterH;

    static public Alignment toAlignment(String s) {
      if ("Center".equals(s)) {
	return Center;
      } else if ("CenterV".equals(s)) {
	return CenterV;
      } else if ("CenterH".equals(s)) {
	return CenterH;
      } else if ("Top".equals(s)) {
	return Top;
      } else if ("Bottom".equals(s)) {
	return Bottom;
      } else if ("Left".equals(s)) {
	return Left;
      } else if ("Right".equals(s)) {
	return Right;
      }
      return null;
    }

  };

  public void alignEdge(GraphEdgeBase edge, Alignment alignment) {
    // System.out.println("GraphDocument.alignEdge() " + alignment);

    if (edge != null) {
      GraphNode src = edge.getSource();
      GraphNode des = edge.getDestination();
      if (src != des && 
	  src instanceof GraphNodeBase && 
	  des instanceof GraphNodeBase) {
	GraphNodeBase n1 = (GraphNodeBase) src;
	GraphNodeBase n2 = (GraphNodeBase) des;
	Rectangle2D b1 = n1.getBounds();
	Rectangle2D b2 = n2.getBounds();
	float x1 = (float) b1.getX();
	float x2 = (float) b2.getX();
	float y1 = (float) b1.getY();
	float y2 = (float) b2.getY();
	float h1 = (float) b1.getHeight();
	float h2 = (float) b2.getHeight();
	float w1 = (float) b1.getWidth();
	float w2 = (float) b2.getWidth();

	Point2D c1 = n1.getCenter();
	Point2D c2 = n2.getCenter();
	float cx1 = (float) c1.getX();
	float cy1 = (float) c1.getY();
	float cx2 = (float) c2.getX();
	float cy2 = (float) c2.getY();

	boolean vertical = false;
	switch (alignment) {
	case Top:
	case Bottom:
	case CenterH:
	  vertical = true;
	  break;
	case Center:
	  if (Math.abs(cx1 - cx2) < Math.abs(cy1 - cy2)) {
	    vertical = false;
	  } else {
	    vertical = true;
	  }
	  break;
	case Left:
	case Right:
	case CenterV:
	  vertical = false;
	  break;
	}

	float dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0;
	float ex1 = cx1, ey1 = cy1, ex2 = cx2, ey2 = cy2;

	if (vertical) {
	  // make sure the src and des nodes do not overlap
	  // horizontally
	  float minhsep = w1 / 2 + w2 / 2 + 50;
	  if (cx1 <= cx2) {
	    if ((cx2 - cx1) < minhsep) {
	      // move the two nodes apart horizontally
	      dx2 = (minhsep - (cx2 - cx1));
	      ex2 += dx2;
	    }
	  } else {
	    if ((cx1 - cx2) < minhsep) {
	      // move the two nodes apart horizontally
	      dx1 = (minhsep - (cx1 - cx2));
	      ex1 += dx1;
	    }
	  }
	} else {
	  // make sure the src and des nodes do not overlap vertically
	  float minvsep = h1 / 2 + h2 / 2 + 50;
	  if (cy1 <= cy2) {
	    if ((cy2 - cy1) < minvsep) {
	      // move the two nodes apart vertically
	      dy2 = (minvsep - (cy2 - cy1));
	      ey2 += dy2;
	    }
	  } else {
	    if ((cy1 - cy2) < minvsep) {
	      // move the two nodes apart vertically
	      dy1 = (minvsep - (cy1 - cy2));
	      ey1 += dy1;
	    }
	  }
	}

	// do alignment
	switch (alignment) {
	case Center:
	case CenterV:
	case CenterH:
	  if (vertical) {
	    float cy = (cy1 + cy2) / 2;
	    dy1 = cy - cy1;
	    dy2 = cy - cy2;
	    cy1 = cy2 = ey1 = ey2 = cy;
	  } else {
	    float cx = (cx1 + cx2) / 2;
	    dx1 = cx - cx1;
	    dx2 = cx - cx2;
	    cx1 = cx2 = ex1 = ex2 = cx;
	  }
	  break;
	case Left:
	  if (x1 <= x2) {
	    dx2 = (x1 - x2);
	    cx2 += dx2;
	  } else {
	    dx1 = (x2 - x1);
	    cx1 += dx1;
	  }
	  if (w1 <= w2) {
	    ex1 = ex2 = cx1;
	  } else {
	    ex1 = ex2 = cx2;
	  }
	  break;
	case Right:
	  if ((x1 + w1) <= (x2 + w2)) {
	    dx1 = ((x2 + w2) - (x1 + w1));
	    cx1 += dx1;
	  } else {
	    dx2 = ((x1 + w1) - (x2 + w2));
	    cx2 += dx2;
	  }
	  if (w1 <= w2) {
	    ex1 = ex2 = cx1;
	  } else {
	    ex1 = ex2 = cx2;
	  }
	  break;
	case Top:
	  if (y1 <= y2) {
	    dy2 = (y1 - y2);
	    cy2 += dy2;
	  } else {
	    dy1 = (y2 - y1);
	    cy1 += dy1;
	  }
	  if (h1 <= h2) {
	    ey1 = ey2 = cy1;
	  } else {
	    ey1 = ey2 = cy2;
	  }
	  break;
	case Bottom:
	  if ((y1 + h1) <= (y2 + h2)) {
	    dy1 = ((y2 + h2) - (y1 + h1));
	    cy1 += dy1;
	  } else {
	    dy2 = ((y1 + h1) - (y2 + h2));
	    cy2 += dy2;
	  }
	  if (h1 <= h2) {
	    ey1 = ey2 = cy1;
	  } else {
	    ey1 = ey2 = cy2;
	  }
	  break;
	}
	n1.moveNotify(dx1, dy1);
	n2.moveNotify(dx2, dy2);
	// edge.setEnds(ex1, ey1, ex2, ey2);
	if (w1 != 0 && h1 != 0) {
	  edge.setTailOffset((ex1 - cx1) / w1, (ey1 - cy1) / h1);
	} else {
	  edge.setTailOffset(0, 0);
	}
	if (w2 != 0 && h2 != 0) {
	  edge.setHeadOffset((ex2 - cx2) / w2, (ey2 - cy2) / h2);
	} else {
	  edge.setHeadOffset(0, 0);
	}
	// layoutAdjacentEdges(src);
	// layoutAdjacentEdges(des);
	Set<GraphEdgeBase> eset = allAdjacentEdges(n1, (Set) null, (Set) null);
	eset = allAdjacentEdges(n2, eset, (Set) null);
	layoutEdges(eset);
      }
    }
  }

  public void alignNodes(GraphNodeBase n1, GraphNodeBase n2, Alignment alignment) {
    // System.out.println("graphDocument.alignNodes()");

    if (n1 != null && n2 != null && n1 != n2) {
      // System.out.println("graphDocument.alignNodes() @1");

      Rectangle2D b1 = n1.getBounds();
      Rectangle2D b2 = n2.getBounds();
      float x1 = (float) b1.getX();
      float x2 = (float) b2.getX();
      float y1 = (float) b1.getY();
      float y2 = (float) b2.getY();
      float h1 = (float) b1.getHeight();
      float h2 = (float) b2.getHeight();
      float w1 = (float) b1.getWidth();
      float w2 = (float) b2.getWidth();

      Point2D c1 = n1.getCenter();
      Point2D c2 = n2.getCenter();
      float cx1 = (float) c1.getX();
      float cy1 = (float) c1.getY();
      float cx2 = (float) c2.getX();
      float cy2 = (float) c2.getY();

      boolean vertical = false;
      switch (alignment) {
      case Top:
      case Bottom:
	vertical = true;
	break;
      case Center:
	if (Math.abs(cx1 - cx2) < Math.abs(cy1 - cy2)) {
	  vertical = false;
	} else {
	  vertical = true;
	}
	break;
      case Left:
      case Right:
	vertical = false;
	break;
      }

      float dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0;

      if (vertical) {
	// make sure the src and des nodes do not overlap horizontally
	float minhsep = w1 / 2 + w2 / 2 + 50;
	if (cx1 <= cx2) {
	  if ((cx2 - cx1) < minhsep) {
	    // move the two nodes apart horizontally
	    dx2 = (minhsep - (cx2 - cx1));
	  }
	} else {
	  if ((cx1 - cx2) < minhsep) {
	    // move the two nodes apart horizontally
	    dx1 = (minhsep - (cx1 - cx2));
	  }
	}
      } else {
	// make sure the src and des nodes do not overlap vertically
	float minvsep = h1 / 2 + h2 / 2 + 50;
	if (cy1 <= cy2) {
	  if ((cy2 - cy1) < minvsep) {
	    // move the two nodes apart vertically
	    dy2 = (minvsep - (cy2 - cy1));
	  }
	} else {
	  if ((cy1 - cy2) < minvsep) {
	    // move the two nodes apart vertically
	    dy1 = (minvsep - (cy1 - cy2));
	  }
	}
      }

      // do alignment
      switch (alignment) {
      case Center:
	if (vertical) {
	  float cy = (cy1 + cy2) / 2;
	  dy1 = cy - cy1;
	  dy2 = cy - cy2;
	} else {
	  float cx = (cx1 + cx2) / 2;
	  dx1 = cx - cx1;
	  dx2 = cx - cx2;
	}
	break;
      case Left:
	if (x1 <= x2) {
	  dx2 = (x1 - x2);
	} else {
	  dx1 = (x2 - x1);
	}
	break;
      case Right:
	if ((x1 + w1) <= (x2 + w2)) {
	  dx1 = ((x2 + w2) - (x1 + w1));
	} else {
	  dx2 = ((x1 + w1) - (x2 + w2));
	}
	break;
      case Top:
	if (y1 <= y2) {
	  dy2 = (y1 - y2);
	} else {
	  dy1 = (y2 - y1);
	}
	break;
      case Bottom:
	if ((y1 + h1) <= (y2 + h2)) {
	  dy1 = ((y2 + h2) - (y1 + h1));
	} else {
	  dy2 = ((y1 + h1) - (y2 + h2));
	}
	break;
      }
      n1.moveNotify(dx1, dy1);
      n2.moveNotify(dx2, dy2);
      // layoutAdjacentEdges(n1);
      // layoutAdjacentEdges(n2);
      Set<GraphEdgeBase> eset = allAdjacentEdges(n1, (Set) null, (Set) null);
      eset = allAdjacentEdges(n2, eset, (Set) null);
      layoutEdges(eset);
    }
  }

  static class GraphNodeComparator implements Comparator<GraphNodeBase> {

    boolean vertical = false;

    public GraphNodeComparator(boolean vertical) {
      this.vertical = vertical;
    }

    public int compare(GraphNodeBase n1, GraphNodeBase n2) {
      if (n1 != null && n2 != null) {
	Point2D c1 = n1.getCenter();
	Point2D c2 = n2.getCenter();
	float cx1 = (float) c1.getX();
	float cy1 = (float) c1.getY();
	float cx2 = (float) c2.getX();
	float cy2 = (float) c2.getY();
	if (vertical) {
	  if (cy1 < cy2) {
	    return -1;
	  } else if (cy1 > cy2) {
	    return 1;
	  }
	} else {
	  if (cx1 < cx2) {
	    return -1;
	  } else if (cx1 > cx2) {
	    return 1;
	  }
	}
      }
      return 0;
    }
  }

  public void alignNodesVertical(Set<GraphNodeBase> nodes, Alignment alignment) {
    if (nodes != null && nodes.size() > 1) {
      int n = nodes.size();
      GraphNodeBase node;
      Iterator<GraphNodeBase> iter = nodes.iterator();
      switch (alignment) {
      case Center:
	float sum = 0;
	while (iter.hasNext()) {
	  node = iter.next();
	  Point2D c = node.getCenter();
	  float x = (float) c.getX();
	  sum += x;
	}
	float cx = sum / n;
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Point2D c = node.getCenter();
	  float x = (float) c.getX();
	  float dx = cx - x;
	  node.moveNotify(dx, 0);
	  // layoutAdjacentEdges(node);
	  Set<GraphEdgeBase> eset = allAdjacentEdges(node, (Set) null,
	      (Set) null);
	  layoutEdges(eset);
	}
	break;
      case Left:
	float min = Float.MAX_VALUE;
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float x = (float) b.getX();
	  min = Math.min(min, x);
	}
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float x = (float) b.getX();
	  float dx = min - x;
	  node.moveNotify(dx, 0);
	  layoutAdjacentEdges(node);
	}
	break;
      case Right:
	float max = Float.MIN_VALUE;
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float x = (float) b.getX();
	  float w = (float) b.getWidth();
	  max = Math.max(max, x + w);
	}
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float x = (float) b.getX();
	  float w = (float) b.getWidth();
	  float dx = max - (x + w);
	  node.moveNotify(dx, 0);
	  layoutAdjacentEdges(node);
	}
	break;
      }

      if (n > 1) {
	List<GraphNodeBase> nlist = new ArrayList<GraphNodeBase>(n);
	nlist.addAll(nodes);
	Collections.sort(nlist, new GraphNodeComparator(true));
	for (int i = 0; i < n - 1; i++) {
	  GraphNodeBase n1 = nlist.get(i);
	  GraphNodeBase n2 = nlist.get(i + 1);
	  alignEdgesBetweenNodes(n1, n2, true);
	}
      }
    }
  }

  public void alignNodesHorizontal(Set<GraphNodeBase> nodes, Alignment alignment) {
    if (nodes != null && nodes.size() > 1) {
      int n = nodes.size();
      GraphNodeBase node;
      Iterator<GraphNodeBase> iter = nodes.iterator();
      switch (alignment) {
      case Center:
	float sum = 0;
	while (iter.hasNext()) {
	  node = iter.next();
	  Point2D c = node.getCenter();
	  float y = (float) c.getY();
	  sum += y;
	}
	float cy = sum / n;
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Point2D c = node.getCenter();
	  float y = (float) c.getY();
	  float dy = cy - y;
	  node.moveNotify(0, dy);
	  layoutAdjacentEdges(node);
	}
	break;
      case Top:
	float min = Float.MAX_VALUE;
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float y = (float) b.getY();
	  min = Math.min(min, y);
	}
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float y = (float) b.getY();
	  float dy = min - y;
	  node.moveNotify(0, dy);
	  layoutAdjacentEdges(node);
	}
	break;
      case Bottom:
	float max = Float.MIN_VALUE;
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float y = (float) b.getY();
	  float h = (float) b.getHeight();
	  max = Math.max(max, y + h);
	}
	iter = nodes.iterator();
	while (iter.hasNext()) {
	  node = iter.next();
	  Rectangle2D b = node.getBounds();
	  float y = (float) b.getY();
	  float h = (float) b.getHeight();
	  float dy = max - (y + h);
	  node.moveNotify(0, dy);
	  layoutAdjacentEdges(node);
	}
	break;
      }

      if (n > 1) {
	List<GraphNodeBase> nlist = new ArrayList<GraphNodeBase>(n);
	nlist.addAll(nodes);
	Collections.sort(nlist, new GraphNodeComparator(false));
	for (int i = 0; i < n - 1; i++) {
	  GraphNodeBase n1 = nlist.get(i);
	  GraphNodeBase n2 = nlist.get(i + 1);
	  alignEdgesBetweenNodes(n1, n2, false);
	}
      }
    }
  }

  public void alignEdgesBetweenNodes(GraphNodeBase n1, GraphNodeBase n2,
      boolean vertical) {
    if (n1 != null && n2 != null && n1 != n2) {
      // System.out.println("GraphDocument.alignEdgesBetweenNodes() @1 " +
      // n1 + " " + n2);

      Set<GraphEdgeBase> edges = edgesBetween(n1, n2);
      if (edges != null) {
	int n = edges.size();
	Iterator<GraphEdgeBase> iter = edges.iterator();
	if (n > 0) {
	  Rectangle2D b1 = n1.getBounds();
	  Rectangle2D b2 = n2.getBounds();
	  float x1 = (float) b1.getX();
	  float x2 = (float) b2.getX();
	  float y1 = (float) b1.getY();
	  float y2 = (float) b2.getY();
	  float h1 = (float) b1.getHeight();
	  float h2 = (float) b2.getHeight();
	  float w1 = (float) b1.getWidth();
	  float w2 = (float) b2.getWidth();

	  Point2D c1 = n1.getCenter();
	  Point2D c2 = n2.getCenter();
	  float cx1 = (float) c1.getX();
	  float cy1 = (float) c1.getY();
	  float cx2 = (float) c2.getX();
	  float cy2 = (float) c2.getY();
	  if (n == 1) {
	    GraphEdgeBase edge = iter.next();
	    GraphNode src = edge.getSource();
	    float ex1 = cx1, ey1 = cy1, ex2 = cx2, ey2 = cy2;
	    if (vertical) {
	      if (w1 <= w2) {
		ex1 = ex2 = cx1;
	      } else {
		ex1 = ex2 = cx2;
	      }
	    } else {
	      if (h1 <= h2) {
		ey1 = ey2 = cy1;
	      } else {
		ey1 = ey2 = cy2;
	      }
	    }

	    /*
	     * System.out.println("GraphDocument.alignEdgesBetweenNodes() @2
	     * w1=" + w1 + " h1=" + h1 + " w2=" + w2 + " h2=" + h2);
	     * System.out.println("GraphDocument.alignEdgesBetweenNodes() @3
	     * cx1=" + cx1 + " cy1=" + cy1 + " cx2=" + cx2 + " cy2=" + cy2);
	     * System.out.println("GraphDocument.alignEdgesBetweenNodes() @4
	     * ex1=" + ex1 + " ey1=" + ey1 + " ex2=" + ex2 + " ey2=" + ey2);
	     * System.out.println("GraphDocument.alignEdgesBetweenNodes() @5
	     * offset1: " + ((ex1 - cx1) / w1) + " " + ((ey1 - cy1) / h1) + "
	     * offset2: " + ((ex2 - cx2) / w2) + " " + ((ey2 - cy2) / h2));
	     * System.out.println("GraphDocument.alignEdgesBetweenNodes() @6 " +
	     * edge);
	     */
	    // edge.setEnd1(ex1, ey1);
	    // edge.setEnd2(ex2, ey2);
	    if (src == n1) {
	      if (w1 != 0 && h1 != 0) {
		edge.setTailOffset((ex1 - cx1) / w1, (ey1 - cy1) / h1);
	      } else {
		edge.setTailOffset(0, 0);
	      }
	      if (w2 != 0 && h2 != 0) {
		edge.setHeadOffset((ex2 - cx2) / w2, (ey2 - cy2) / h2);
	      } else {
		edge.setHeadOffset(0, 0);
	      }
	    } else {
	      if (w1 != 0 && h1 != 0) {
		edge.setHeadOffset((ex1 - cx1) / w1, (ey1 - cy1) / h1);
	      } else {
		edge.setHeadOffset(0, 0);
	      }
	      if (w2 != 0 && h2 != 0) {
		edge.setTailOffset((ex2 - cx2) / w2, (ey2 - cy2) / h2);
	      } else {
		edge.setTailOffset(0, 0);
	      }
	    }
	    edge.doLayout();
	    // edge.adjustEnds();
	  } else {

	  }
	}
      }
    }
  }

  // ///////////////

  public ModelElement getModel() {
    return model;
  }

  public void setModel(ModelElement model) {
    if (this.model != null) {
      ModelRepository rep = ModelRepository.getInstance();
      rep.removeElement(this.model);
    }

    this.model = model;
  }

  /**
   * Reconstruct the association of model elements based on the graph
   * connectivity.
   * 
   * Each shape (node/edge) has an associated model element. The values
   * are edited and maintained for each shape locally. The model of the
   * diagrams are not maintained to be consistent with the graph. It is
   * necessary to reconstruct the model of the diagrams before save/export
   * the diagram.
   * 
   * The model of basic shapes (not hyper nodes) should not be
   * deconstructed. Only the model of a dragram or a hyper node should be
   * deconstructed.
   * 
   */
  public void constructModel() {
    if (model instanceof UMLDiagram) {
      UMLDiagram diagram = (UMLDiagram) model;
      model.deconstruct();

      // handling nodes in first pass
      Iterator<DrawShape> iter = shapes.iterator();
      while (iter.hasNext()) {
	DrawShape shape = iter.next();
	if (shape instanceof GraphNodeBase) {
	  constructModel(diagram, null, shape);
	}
      }

      // handling all other shaps in second pass
      iter = shapes.iterator();
      while (iter.hasNext()) {
	DrawShape shape = iter.next();
	if (!(shape instanceof GraphNodeBase)) {
	  constructModel(diagram, null, shape);
	}
      }

      if (model instanceof SequenceDiagram) {
	UMLSequenceDiagramUtil.setTiming(this);
      }
    }
  }

  public void constructModel(UMLDiagram diagram, 
			     ModelElement pmodel,
			     DrawShape shape) {
    constructModel(diagram, pmodel, shape, null);
  }

  public void constructModel(UMLDiagram diagram, 
			     ModelElement pmodel,
			     DrawShape shape, 
			     Object arg) {
    if (diagram != null && shape != null) {
      if (debug) { 
	System.out.println("GraphDocument.constructModel()");
      }

      ModelElement p = pmodel;
      ModelElement smodel = shape.getModel();
      ModelListener mlistener = shape.getModelListener();
      if (smodel != null) {
	if (mlistener != null) {
	  mlistener.initModel();
	  mlistener.updateModel();
	}
	diagram.add(pmodel, smodel, arg);
	p = smodel;

	if (smodel.isDestructable()) {
	  List<DrawShape> subshapes = shape.getShapes();
	  if (subshapes != null) {
	    Iterator<DrawShape> iter = subshapes.iterator();
	    while (iter.hasNext()) {
	      DrawShape s = iter.next();
	      if (shape instanceof OrthogonalStateShape) {
		// Orthogonal_State
		OrthogonalStateShape cs = (OrthogonalStateShape) shape;
		// int i = cs.inHorizontalCompartment(s);
		int i = cs.inRegion(s);

		if (debug) {
		  System.out.println("GraphDocument.constructModel(): Orthogonal state: region="
				     + i + "  " + s);
		}

		if (i >= 0) {
		  constructModel(diagram, p, s, i);
		} else {
		  constructModel(diagram, p, s);
		}
	      } else {
		constructModel(diagram, p, s);
	      }
	    }
	  }
	}
      }
    }
  }

  public boolean exportModel(String filename, boolean checkExists) {
    if (debug) {
      System.out.println("GraphDocument.exportModel(): filename=" + filename);
    }

    if (model != null && filename != null) {

      constructModel();

      if (debug) {
	System.out.println("GraphDocument.exportModel(): model != null");
      }

      try {
	if (checkExists) {
	  if (!checkWriteFile(filename)) {
	    return false;
	  }
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
	    filename)));
	ModelRepository rep = ModelRepository.getInstance();
	rep.exportXML(out, model);
	out.close();
	return true;
      } catch (IOException e) {
	System.out.println("Unable to write file: " + filename);
	// e.printStackTrace();
      } catch (Exception e) {
	System.out.println("Unable to write file: " + filename);
	// e.printStackTrace();
      }
    }
    return false;
  }

  public boolean exportXMI(String filename, boolean checkExists) {
    if (debug) {
      System.out.println("GraphDocument.exportModel(): filename=" + filename);
    }

    if (model != null && filename != null) {

      constructModel();

      if (debug) {
	System.out.println("GraphDocument.exportModel(): model != null");
      }

      try {
	if (checkExists) {
	  if (!checkWriteFile(filename)) {
	    return false;
	  }
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(filename)));
	ModelRepository rep = ModelRepository.getInstance();
	rep.exportXMI(out, model);
	out.close();
	return true;
      } catch (IOException e) {
	System.out.println("Unable to write file: " + filename);
	// e.printStackTrace();
      } catch (Exception e) {
	System.out.println("Unable to write file: " + filename);
	// e.printStackTrace();
      }
    }
    return false;
  }

  public void dumpModel() {
    if (debug) {
      System.out.println("GraphDocument.dumpModel()");
    }

    constructModel();

    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    int n = shapes.size();
    for (int i = 0; i < n; i++) {
      DrawShape shape = shapes.get(i);

      String id = null;
      if (shape instanceof GraphNodeBase) {
	GraphNodeBase node = (GraphNodeBase) shape;
	id = node.getId();
      } else if (shape instanceof GraphEdgeBase) {
	GraphEdgeBase edge = (GraphEdgeBase) shape;
	id = edge.getId();
      }
      out.println("-- shape name: " + shape.getName() + " id: " + id);

      /*
       * ModelElement model = shape.getModel(); if (model != null) {
       * out.println("-- model: " + model.toString());
       * 
       * model.exportXML(out, 0); }
       */
    }
    out.println();

    out.println("==================== Model Dump ===============");

    out.println("-- model: " + model.toString() + "-- dump --");
    ((AbstractModelElement) model).dump(out, 0);

    out.println("==================== Model Repository ===============");

    ModelRepository repository = ModelRepository.getInstance();
    repository.exportXML(out);

    out.flush();

    repository.dump();

  }

  /* Read/write XML */

  @Override
  public void writeXML(CodeWriter out, int mode) 
    throws IOException, DocumentException {
    if (debug) {
      System.out.println("GraphDocument.writeXML()");
    }

    if (out != null) {
      try {
	out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>");
	writeXMLDocumentHeader(out);
	writeXMLDocumentProperties(out);
	writeXMLModel(out, useDefaultNamespace ? null : namespacePrefix);
	writeXMLDocumentShapes(out, mode, 
			       useDefaultNamespace ? null : namespacePrefix);
	writeXMLDocumentGraph(out, mode, 
			      useDefaultNamespace ? null : namespacePrefix);
	out.thisIndentDec();
	writeXMLDocumentFooter(out);
      } catch (Exception e) {
	throw new DocumentException("Error in saving document", e);
      }
    }
  }

  public void writeXMLDocumentGraph(CodeWriter out, int mode,
				    String namespacePrefix) {
    if (out != null) {
      String nodename = DrawShapeBase.getPrefixedName("Graph", namespacePrefix);
      if (graph != null) {
	out.println("<" + nodename + ">");
	// " id=\"" + graph.getId() + "\">");
	out.thisIndentInc();

	GraphLayoutManager glm = getGraphLayoutManager();
	//System.out.println("GraphDocument.writeXMLDocumentGraph() glm == null " + (glm == null));

	if (glm != null) { 
	  String glmNodename = DrawShapeBase.getPrefixedName("Layout", namespacePrefix);
	  out.print("<" + glmNodename);
	  if (continuousLayout) { 
	    out.print(" continuous=\"" + continuousLayout + "\"");
	  }
	  out.println(" name=\"" + glm.getClass().getName() + "\"/>");
	}

	ViewOptions opt = getViewOptions();
	if (opt != null) { 
	  String optNodename = DrawShapeBase.getPrefixedName("ViewOptions", namespacePrefix);
	  out.print("<" + optNodename);
	  out.println(" name=\"" + opt.getClass().getName() + "\"/>");
	}

	graph.writeSubgraphXML(out, namespacePrefix);
	out.thisIndentDec();
	out.println("</" + nodename + ">");
      } else {
	out.println("<" + nodename + "/>");
      }
    }
  }

  public void writeSubgraphXML(CodeWriter out, String namespacePrefix) {
    graph.writeSubgraphXML(out, namespacePrefix);
  }

  public void buildSubgraphFromXML(org.w3c.dom.Node node, String uri0,
				   Map compShapeMap) {
    if (graph != null) { 
      graph.buildSubgraphFromXML(node, uri0, compShapeMap);
    }
  }

  @Override
  protected void processDocumentChild(org.w3c.dom.Node node, String uri0) {
    if (node != null) {
      if (debug) {
	System.out.println("GraphDocument.processDocumentChild(): " + node.getLocalName() + " uri0=" + uri0);
      }

      String name = node.getLocalName();
      if ("Graph".equals(name)) {
	// build hash table: compid -> shape
	Map compShapeMap = new HashMap();
	Iterator iter = shapes.iterator();
	while (iter.hasNext()) {
	  DrawShape shape = (DrawShape) iter.next();
	  buildCompMap(shape, compShapeMap);
	}

	org.w3c.dom.NodeList graphNodes = node.getChildNodes();
	if (graphNodes != null) {
	  int m = graphNodes.getLength();
	  for (int j = 0; j < m; j++) {
	    org.w3c.dom.Node gnode = graphNodes.item(j);
	    String uri = gnode.getNamespaceURI();
	    if (debug) {
	      System.out.println("GraphDocument.processDocumentChild(): [" + j + "] " + 
				 (gnode != null ? gnode.getLocalName() : "null") + 
				 " uri=" + uri);
	    }

	    if (gnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
		(uri0 == null || uri0.equals(uri))) {
	      if ("Subgraph".equals(gnode.getLocalName())) {
		buildSubgraphFromXML(gnode, uri0, compShapeMap);
	      } else if ("Layout".equals(gnode.getLocalName())) {
		org.w3c.dom.Element e = (org.w3c.dom.Element) gnode;
		String glmName = e.getAttribute("name");
		setGraphLayoutManager(glmName);
		//System.out.println("GraphDocument.processDocumentChild() glmName " + glmName);

		String s = e.getAttribute("continuous");
		if (s != null && s.length() > 0) {
		  continuousLayout = Boolean.valueOf(s);
		}
	      } else if ("ViewOptions".equals(gnode.getLocalName())) {
		org.w3c.dom.Element e = (org.w3c.dom.Element) gnode;
		String optName = e.getAttribute("name");
		setViewOptions(optName);
	      }
	    }

	    iter = shapes.iterator();
	    while (iter.hasNext()) {
	      DrawShape s = (DrawShape) iter.next();
	      if (s instanceof GraphComponentShape) {
		GraphComponentShape comp = (GraphComponentShape) s;
		comp.setSubgraph(this);
		comp.setGraph(this);
		if (s instanceof GraphHyperNodeBase) { 
		  GraphHyperNodeBase hnode = (GraphHyperNodeBase) s;
		  hnode.setParentOfGraphComponents(this);
		}
	      }
	    }
	  }
	}
      } else if ("Model".equals(name)) {
	org.w3c.dom.NodeList modelNodes = node.getChildNodes();
	if (modelNodes != null) {
	  int m = modelNodes.getLength();
	  for (int j = 0; j < m; j++) {
	    org.w3c.dom.Node mnode = modelNodes.item(j);
	    if (mnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
	      ModelElement root = ModelBuilder.loadElementFromXML(mnode);
	      ModelRepository rep = ModelRepository.getInstance();
	      if (root instanceof RootElement) {
		rep.addModel((RootElement) root);
	      }
	      rep.resolveLinks();
	      setModel(root);
	      break;
	    }
	  }
	}
      }
    }
  }

  protected void buildCompMap(DrawShape shape, Map compShapeMap) {
    if (shape != null && compShapeMap != null) {
      if (shape instanceof GraphComponentShape) {
	GraphComponentShape comp = (GraphComponentShape) shape;
	if (comp.getId() != null) {
	  compShapeMap.put(comp.getId(), comp);
	}
      }
      List subshapes = shape.getShapes();
      if (subshapes != null) {
	Iterator iter = subshapes.iterator();
	while (iter.hasNext()) {
	  DrawShape s = (DrawShape) iter.next();
	  buildCompMap(s, compShapeMap);
	}
      }
    }
  }

  public void writeXMLModel(CodeWriter out, String namespacePrefix) {
    if (out != null && model != null) {

      constructModel();

      String nodename = DrawShapeBase.getPrefixedName("Model", namespacePrefix);
      out.println("<" + nodename + ">");
      out.thisIndentInc();

      PrintWriter pout = out.getPrintWriter();
      pout.flush();
      model.exportXML(pout, 2);
      pout.flush();

      out.thisIndentDec();
      out.println("</" + nodename + ">");
    }
  }

  protected Graph<GraphNodeBase, GraphEdgeBase> graph;

  protected GraphNodeBase selectedNode;

  protected ModelElement model;

  protected boolean continuousLayout = false; 

  protected static final boolean debug = false;

}
